/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * The sample smart contract for documentation topic:
 * Writing Your First Blockchain Application
 */

package main

/* Imports
 * 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
 * 2 specific Hyperledger Fabric specific libraries for Smart Contracts
 */
import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
)

// Define the Smart Contract structure
type SmartContract struct {
}

// Define the car structure, with 4 properties.  Structure tags are used by encoding/json library
type Car struct {
	Make   string `json:"make"`
	Model  string `json:"model"`
	Colour string `json:"colour"`
	Owner  string `json:"owner"`
	Price  string `json:"price"`
}

type Product struct {
	//ProductId   string `json:"productId"`  //prefix PD   index
	Input    string `json:"input"` //entityid
	Output   string `json:"output"`
	Time     string `json:"time"`
	State    string `json:"state"`
	Level    string `json:"level"`
	Operator string `json:"operator"` //EmployeId
	Others   string `json:"others"`
}

type Entity struct {
	//EntityId   string `json:"entityid"`  //prefix ETD   index
	Name string `json:"name"`
	//Level  string `json:"level"`
	Address string `json:"address"`
	Proxies string `json:"proxies"`
}

type Employee struct {
	//EmployeId   string `json:"employeid"`  //prefix EPD  index
	EntityId string `json:"entityid"`
	Name     string `json:"name"`
	Alias    string `json:"alias"`
	Address  string `json:"address"`
	Monitor  string `json:"monitor"`
}

/*
   Store a product
   args : 1, product id from scan code; 2, employee name ; 3 other infos

*/
func (s *SmartContract) inputProduct(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	return shim.Success(nil)
}

/*
   Shift a product
   args : 1, product id from scan code; 2, employee name ; 3 target Entity id ;4 other infos
*/
func (s *SmartContract) outputProduct(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	return shim.Success(nil)
}

/*
   track a product
   args : 1, product id from scan code; 2, employee name ; 3 other infos
*/
func (s *SmartContract) trackProduct(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	return shim.Success(nil)
}

/*
   Query proxies  of a entity id;
   args : 1, Entity id; return company ids
*/
func (s *SmartContract) queryProxies(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	return shim.Success(nil)
}

/*
   args : 1, employee name; return Employee Id,Entity id
*/
func (s *SmartContract) queryEmployee(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	return shim.Success(nil)
}

/*
 * The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network
 * Best practice is to have any Ledger initialization in separate function -- see initLedger()
 */
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}

/*
 * The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
 * The calling application program has also specified the particular smart contract function to be called, with arguments
 */
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

	// Retrieve the requested Smart Contract function and arguments
	function, args := APIstub.GetFunctionAndParameters()
	// Route to the appropriate handler function to interact with the ledger appropriately
	if function == "queryCar" {
		return s.queryCar(APIstub, args)
	} else if function == "initLedger" {
		return s.initLedger(APIstub)
	} else if function == "createCar" {
		return s.createCar(APIstub, args)
	} else if function == "queryAllCars" {
		return s.queryAllCars(APIstub)
	} else if function == "changeCarOwner" {
		return s.changeCarOwner(APIstub, args)
	} else if function == "count" {
		return s.count(APIstub, args)
	} else if function == "track" {
		return s.track(APIstub, args)
	} else if function == "inputProduct" { //the following items for new app
		return s.inputProduct(APIstub, args)
	} else if function == "outputProduct" {
		return s.outputProduct(APIstub, args)
	} else if function == "trackProduct" {
		return s.trackProduct(APIstub, args)
	} else if function == "queryProxies" {
		return s.queryProxies(APIstub, args)
	} else if function == "queryEmployee" {
		return s.queryEmployee(APIstub, args)
	} else if function == "createEntity" {
		return s.createEntity(APIstub, args)
	} else if function == "createEmployee" {
		return s.createEmployee(APIstub, args)
	} else if function == "version" {
		return s.version(APIstub, args)
	}

	return shim.Error("Invalid Smart Contract function name.")
}

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	return shim.Success(carAsBytes)
}

func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
	cars := []Car{
		Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko", Price: "$500"},
		Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad", Price: "$500"},
		Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo", Price: "$500"},
		Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max", Price: "$500"},
		Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana", Price: "$500"},
		Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel", Price: "$500"},
		Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav", Price: "$500"},
		Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari", Price: "$500"},
		Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria", Price: "$500"},
		Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro", Price: "$500"},
	}

	i := 0
	for i < len(cars) {
		fmt.Println("i is ", i)
		carAsBytes, _ := json.Marshal(cars[i])
		APIstub.PutState("ACAR"+strconv.Itoa(i), carAsBytes)
		fmt.Println("Added", cars[i])
		i = i + 1
	}

	return shim.Success(nil)
}

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 6 {
		return shim.Error("Incorrect number of arguments. Expecting 6")
	}

	var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4], Price: args[5]}

	carAsBytes, _ := json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

func (s *SmartContract) createEntity(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	var err error

	if len(args) != 4 {
		return shim.Error("Incorrect number of arguments. Expecting 4")
	}

	entityAsBytes, err := APIstub.GetState(args[0])
	if err != nil {
		return shim.Error("Failed to get Entity:" + err.Error())
	} else if entityAsBytes != nil {
		return shim.Error("The Entity already exists")
	}

	entity := &Entity{args[1], args[2], args[3]}

	entityAsBytes, err = json.Marshal(entity)
	if err != nil {
		return shim.Error(err.Error())
	}

	err = APIstub.PutState(args[0], entityAsBytes)
	if err != nil {
		return shim.Error("Create Entity Failed")
	}

	return shim.Success(nil)
}

func (s *SmartContract) createEmployee(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	var err error

	if len(args) != 6 {
		return shim.Error("Incorrect number of arguments. Expecting 6")
	}

	employeeAsBytes, err := APIstub.GetState(args[0])
	if err != nil {
		return shim.Error("Failed to get Employee:" + err.Error())
	} else if employeeAsBytes != nil {
		return shim.Error("The Employee already exists")
	}

	employee := &Employee{args[1], args[2], args[3], args[4], args[5]}

	employeeAsBytes, err = json.Marshal(employee)
	if err != nil {
		return shim.Error(err.Error())
	}

	err = APIstub.PutState(args[0], employeeAsBytes)
	if err != nil {
		return shim.Error("Create Employee Failed")
	}

	return shim.Success(nil)
}

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

	startKey := "CAR0"
	endKey := "CAR999"

	resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- queryAllCars:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}

func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 2 {
		return shim.Error("Incorrect number of arguments. Expecting 2")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	car := Car{}

	json.Unmarshal(carAsBytes, &car)
	car.Owner = args[1]

	carAsBytes, _ = json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

func (s *SmartContract) count(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	var resultsIterator shim.StateQueryIteratorInterface
	var err error
	//var resultsIterator  StateQueryIteratorInterface
	//startKey := "CAR0"
	//endKey := "CAR999"
	if len(args) > 2 {
		return shim.Error("Incorrect number of arguments. Expecting 2")
	} else if len(args) == 2 {
		resultsIterator, err = APIstub.GetStateByRange(args[0], args[1])
	} else if len(args) < 2 {
		resultsIterator, err = APIstub.GetStateByRange("", "")
	}

	//resultsIterator, err := APIstub.GetStateByRange(args[0], args[1])
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	count := 0
	buffer.WriteString("[")

	for resultsIterator.HasNext() {
		_, err = resultsIterator.Next()
		count++
	}
	buffer.WriteString("{\"Count\":")
	buffer.WriteString(strconv.Itoa(count))
	buffer.WriteString("]")

	fmt.Printf("- count:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}

func (s *SmartContract) track(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 2")
	}

	resultsIterator, err := APIstub.GetHistoryForKey(args[0])

	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"TxId\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.TxId)
		buffer.WriteString("\"")

		buffer.WriteString(", \"StringValue\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString(", \"Timestamp\":")
		buffer.WriteString(queryResponse.Timestamp.String())
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- track:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())

}

func (s *SmartContract) version(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
	return shim.Success([]byte("3.1"))
}

// The main function is only relevant in unit test mode. Only included here for completeness.
func main() {

	// Create a new Smart Contract
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}
